package com.fw.service.passwordpolicy.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.beans.factory.annotation.Autowired;

import com.fw.service.bean.ErrorBean;
import com.fw.service.passwordpolicy.PasswordPolicyService;
import com.fw.service.passwordpolicy.bean.PasswordPolicyBean;
import com.fw.util.SpringPropertyUpdater;

/**
 * Description: Password Policy
 * 
 * https://www.owasp.org/index.php/Password_special_characters
 * 
 * The same list as string (between double quotes): " !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"
 * 
 * @author AS073
 *
 */
public class PasswordPolicyServiceImpl implements PasswordPolicyService {

    @Autowired
    private Properties passwordPolicyProperties;

    @Autowired
    private SpringPropertyUpdater springPropertyUpdater;

    private int countMatch(String value, String stringPattern) {
	Pattern pattern = Pattern.compile(stringPattern);
	Matcher matcher = pattern.matcher(value);
	int count = 0;
	while (matcher.find()) {
	    count++;
	}
	return count;
    }

    @Override
    public List<ErrorBean> validatePasswordPolicy(String password) {
	List<ErrorBean> errors = null;
	if (password != null) {
	    PasswordPolicyBean passwordPolicyBean = this.getPasswordPolicy();
	    if (passwordPolicyBean != null) {
		Integer minPasswordLength = passwordPolicyBean.getMinPasswordLength();
		// Integer enforePasswordHistory = passwordPolicyBean.getEnforePasswordHistory();
		Integer minUpperCase = passwordPolicyBean.getMinUpperCase();
		Integer minNumeric = passwordPolicyBean.getMinNumeric();
		Integer minSpecialCharacter = passwordPolicyBean.getMinSpecialCharacter();
		// Integer passwordExpiryPeriodDay = passwordPolicyBean.getPasswordExpiryPeriodDay();
		// Integer adminLoingExpirationDay = passwordPolicyBean.getAdminLoingExpirationDay();
		// Integer merchantLoingExpirationDay = passwordPolicyBean.getMerchantLoingExpirationDay();
		// Integer loginAttemptAllowed = passwordPolicyBean.getLoginAttemptAllowed();
		if (minPasswordLength != null && password.length() < minPasswordLength) {
		    if (errors == null) {
			errors = new ArrayList<ErrorBean>();
		    }
		    errors.add(this.createError("minPasswordLength", minPasswordLength));
		}
		if (minUpperCase != null && this.countMatch(password, "[A-Z]") < minUpperCase) {
		    if (errors == null) {
			errors = new ArrayList<ErrorBean>();
		    }
		    errors.add(this.createError("minUpperCase", minUpperCase));
		}
		if (minNumeric != null && this.countMatch(password, "[0-9]") < minNumeric) {
		    if (errors == null) {
			errors = new ArrayList<ErrorBean>();
		    }
		    errors.add(this.createError("minNumeric", minNumeric));
		}
		if (minSpecialCharacter != null && this.countMatch(password, "[ !\"#$%&'()*+,-./:;<=>?@[\b]^_`{|}~]") < minSpecialCharacter) {
		    if (errors == null) {
			errors = new ArrayList<ErrorBean>();
		    }
		    errors.add(this.createError("minSpecialCharacter", minSpecialCharacter));
		}
	    }
	}
	return errors;
    }

    private ErrorBean createError(String fieldName, Integer value) {
	ErrorBean error = new ErrorBean();
	error.setErrorCode(PasswordPolicyBean.class.getName() + "." + fieldName);
	error.setErrorParameters(new String[] { String.valueOf(value) });
	return error;
    }

    private Integer getPropertyValue(String fieldName) {
	Integer integerValue = null;
	String stringValue = passwordPolicyProperties.getProperty(PasswordPolicyBean.class.getName() + "." + fieldName, null);
	if (stringValue != null) {
	    integerValue = Integer.valueOf(stringValue);
	}
	return integerValue;
    }

    private void setPropertyValue(String fieldName, Integer integerValue) {
	if (integerValue != null) {
	    passwordPolicyProperties.setProperty(PasswordPolicyBean.class.getName() + "." + fieldName, String.valueOf(integerValue));
	}
    }

    @Override
    public PasswordPolicyBean getPasswordPolicy() {
	PasswordPolicyBean passwordPolicyBean = new PasswordPolicyBean();
	passwordPolicyBean.setMinPasswordLength(this.getPropertyValue("minPasswordLength"));
	passwordPolicyBean.setEnforePasswordHistory(this.getPropertyValue("enforePasswordHistory"));
	passwordPolicyBean.setMinUpperCase(this.getPropertyValue("minUpperCase"));
	passwordPolicyBean.setMinNumeric(this.getPropertyValue("minNumeric"));
	passwordPolicyBean.setMinSpecialCharacter(this.getPropertyValue("minSpecialCharacter"));
	passwordPolicyBean.setPasswordExpiryPeriodDay(this.getPropertyValue("passwordExpiryPeriodDay"));
	passwordPolicyBean.setAdminLoingExpirationDay(this.getPropertyValue("adminLoingExpirationDay"));
	passwordPolicyBean.setMerchantLoingExpirationDay(this.getPropertyValue("merchantLoingExpirationDay"));
	passwordPolicyBean.setLoginAttemptAllowed(this.getPropertyValue("loginAttemptAllowed"));
	passwordPolicyBean.setPasswordNotificationExpireDay(this.getPropertyValue("passwordNotificationExpireDay"));
	return passwordPolicyBean;
    }

    @Override
    public PasswordPolicyBean setPasswordPolicy(PasswordPolicyBean passwordPolicyBean) {
	if (passwordPolicyBean != null) {
	    this.setPropertyValue("minPasswordLength", passwordPolicyBean.getMinPasswordLength());
	    this.setPropertyValue("enforePasswordHistory", passwordPolicyBean.getEnforePasswordHistory());
	    this.setPropertyValue("minUpperCase", passwordPolicyBean.getMinUpperCase());
	    this.setPropertyValue("minNumeric", passwordPolicyBean.getMinNumeric());
	    this.setPropertyValue("minSpecialCharacter", passwordPolicyBean.getMinSpecialCharacter());
	    this.setPropertyValue("passwordExpiryPeriodDay", passwordPolicyBean.getPasswordExpiryPeriodDay());
	    this.setPropertyValue("adminLoingExpirationDay", passwordPolicyBean.getAdminLoingExpirationDay());
	    this.setPropertyValue("merchantLoingExpirationDay", passwordPolicyBean.getMerchantLoingExpirationDay());
	    this.setPropertyValue("loginAttemptAllowed", passwordPolicyBean.getLoginAttemptAllowed());
	    this.setPropertyValue("passwordNotificationExpireDay", passwordPolicyBean.getPasswordNotificationExpireDay());

	    // update properties
	    try {
		springPropertyUpdater.updateProperties("passwordPolicyProperties", passwordPolicyProperties, "");
	    } catch (Exception e) {
		e.printStackTrace();
	    }
	}
	return passwordPolicyBean;
    }

    @Override
    public ErrorBean validateInputPassword(String password) {
	ErrorBean error = null;
	if (password != null && this.countMatch(password, "[^a-zA-Z0-9 !\"#$%&'()*+,-./:;<=>?@[\b]^_`{|}~]") > 0) {
	    error = new ErrorBean();
	    error.setErrorCode("errors.validator.passwordpolicy.inputPassword");
	}
	return error;
    }

    private final static List<Character> LOWER_CAPS, UPPER_CAPS, DIGITS, SPECIALS;

    static {
	LOWER_CAPS = new ArrayList<Character>(26);
	UPPER_CAPS = new ArrayList<Character>(26);
	for (int i = 0; i < 26; i++) {
	    LOWER_CAPS.add((char) (i + 'a'));
	    UPPER_CAPS.add((char) (i + 'A'));
	}

	DIGITS = new ArrayList<Character>(10);
	for (int i = 0; i < 10; i++) {
	    DIGITS.add((char) (i + '0'));
	}

	SPECIALS = new ArrayList<Character>() {
	    /**
	     * 
	     */
	    private static final long serialVersionUID = 1L;

	    {
		add('!');
		add('#');
		add('$');
		add('%');
		add('&');
		add('(');
		add(')');
		add('*');
		add('+');
		add(',');
		add('-');
		add('.');
		add('/');
		add(':');
		add(';');
		add('<');
		add('=');
		add('>');
		add('?');
		add('@');
		add('^');
		add('_');
		add('`');
		add('{');
		add('|');
		add('}');
		add('~');

	    }
	};
    }

    public String generatePassword(Boolean shuffle) {
	Integer minLength = this.getPropertyValue("minPasswordLength");
	Integer minUpperCase = this.getPropertyValue("minUpperCase");
	Integer minNumeric = this.getPropertyValue("minNumeric");
	Integer minSpecialChar = this.getPropertyValue("minSpecialCharacter");

	if (minLength == null) {
	    minLength = 0;
	}
	if (minUpperCase == null) {
	    minUpperCase = 0;
	}
	if (minNumeric == null) {
	    minNumeric = 0;
	}
	if (minSpecialChar == null) {
	    minSpecialChar = 0;
	}

	List<Character> password = new ArrayList<Character>();
	Random random = new Random();
	for (int i = 0; i < minUpperCase; i++) {
	    password.add(UPPER_CAPS.get(random.nextInt(UPPER_CAPS.size())));
	}
	for (int i = 0; i < minNumeric; i++) {
	    password.add(DIGITS.get(random.nextInt(DIGITS.size())));
	}
	for (int i = 0; i < minSpecialChar; i++) {
	    password.add(SPECIALS.get(random.nextInt(SPECIALS.size())));
	}

	int pwdLength = minLength - minUpperCase - minNumeric - minSpecialChar;
	if (pwdLength <= 0) {
	    pwdLength = 3;
	}
	for (int i = 0; i < pwdLength; i++) {
	    password.add(LOWER_CAPS.get(random.nextInt(LOWER_CAPS.size())));
	}

	if (shuffle) {
	    Collections.shuffle(password);
	}

	StringBuilder passwordBuilder = new StringBuilder();
	for (Character chr : password) {
	    passwordBuilder.append(chr);
	}
	return passwordBuilder.toString();

    }

}
